﻿package extremefx.modules {
	import extremefx.anim.FrameEventDispatcher;
	import extremefx.anim.TickEventArgs;
	import extremefx.events.EventArgs;
	import extremefx.geom.Size;
	import extremefx.geom.XPoint;
	import extremefx.modules.seaDragon.DisplayRect;
	import extremefx.modules.seaDragon.Drawer;
	import extremefx.modules.seaDragon.DziTileSource;
	import extremefx.modules.seaDragon.Viewport;
	import extremefx.storage.XmlLoader;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;		

	/**
	 * @author Marcelo Volmaro
	 */
	public class DeepZoom {
		private var _dispRects:Vector.<DisplayRect>;
		private var _source:DziTileSource;
		private var _drawer:Drawer;
		private var _viewPort:Viewport;
		private var _tilesUrl:String;
		private var _canvas:Sprite;
		private var _loader:XmlLoader;
		private var _size:Size;
		private var _animating:Boolean;
		private var _forceRedraw:Boolean;
		private var _lastMouseDownPoint:*;
		private var _lastPoint:XPoint;
		private var _lastMouseDownTime:Number;
		private var _clickIn:Boolean;
		private var _pos:Point;

		/**
		 * @param pWidth total width of the image
		 * @param pHeight total height of the image
		 */
		public function DeepZoom(pHolder:Sprite, pBackgroundColor:uint = 0) {
			var s:Sprite = new Sprite();
			
			pHolder.addChild(s);
			s.opaqueBackground = pBackgroundColor;
			_canvas = s;
			_size = new Size(1,1);
			_drawer = new Drawer();
		}
		
		public function zoomTo(pZoom:Number, pRefPoint:XPoint = null, pImmediately:Boolean = false):void {
			_viewPort.zoomTo(pZoom, pRefPoint, pImmediately);
		}

		public function panBy(pDelta:XPoint):void {
			_viewPort.panBy(_viewPort.deltaPointsFromPixels(pDelta.negate()));
		}
		
		public function getZoom(pFromCurrent:Boolean = false):Number {
			return _viewPort.getZoom(pFromCurrent);
		}

		public function zoomBy(pFactor:Number, pPosition:XPoint = null, pImmediately:Boolean = false):void {
			if (pPosition == null) {
				pPosition = _viewPort.getCenter();
				
			} else {
				pPosition = _viewPort.pointFromPixel(pPosition, true);
			}
			
			_viewPort.zoomBy(pFactor, pPosition, pImmediately);
			_viewPort.ensureVisible();
		}
		
		public function fitBounds(pBounds:Rectangle, pImmediately:Boolean = false):void {
			_viewPort.fitBounds(pBounds, pImmediately);
		}
		
		public function addOverlay(pDisplayObject:DisplayObject, pLocation:Rectangle, pPlacement:uint = 0):void {
			_drawer.addOverlay(pDisplayObject, pLocation, pPlacement);
		}
		
		public function goHome(pImmediately:Boolean = false):void {
			_viewPort.goHome(pImmediately);
		}

		public function init(pFullSize:Size, pTileSize:uint, pTileOverlay:uint, pFileFormat:String, pDisplayRectangles:Vector.<DisplayRect> = null):void {
			_dispRects = pDisplayRectangles || new Vector.<DisplayRect>();
			_source = new DziTileSource(pFullSize, pTileSize, pTileOverlay, _tilesUrl, pFileFormat, _dispRects);
			_viewPort = new Viewport(_size, pFullSize);
			_canvas.scrollRect = new Rectangle(0, 0, _size.width, _size.height);
			_drawer.init(_source, _viewPort, _canvas);
			
			_forceRedraw = true;
			FrameEventDispatcher.onTick.add(_update);
			_update(null, null);
			var s:Stage = _canvas.stage;
			
			s.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown, false, 0, true);
			s.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp, false, 0, true);
			_canvas.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseScroll, false, 0, true);
			s.addEventListener(Event.MOUSE_LEAVE, _onMouseLeave, false, 0, true);
		}
		
		public function set position(pPosition:Point):void {
			if (pPosition == null) return;
			
			_canvas.x = pPosition.x;
			_canvas.y = pPosition.y;
			_pos = pPosition;
		}

		public function get position():Point {
			return _pos;
		}
		
		public function set size(pSize:Size):void {
			if (pSize == null) return;
			_size = pSize;
			_canvas.scrollRect = new Rectangle(0, 0, pSize.width, pSize.height);
			if (_viewPort) _viewPort.resize(pSize);
		}
		
		public function get size():Size {
			return _size;
		}
		
		private function _onMouseLeave(event:Event):void {
			if (!_clickIn) return;
			
			_mouseUp(_lastPoint,false);
		}
		
		private function _onMouseScroll(event:MouseEvent):void {
			event.stopPropagation();
			event.preventDefault();
			
			var factor:Number = Math.pow(1.2, event.delta);
			zoomBy(factor, new XPoint(event.localX, event.localY));
		}
		
		private function _onMouseDown(event:MouseEvent):void {
			var p:Point = new Point(event.stageX, event.stageY);
			_lastMouseDownPoint = new XPoint(_canvas.globalToLocal(p));
			_lastPoint = new XPoint(event.stageX, event.stageY);
			_lastMouseDownTime = getTimer();
			
			var r:Rectangle = _canvas.getBounds(_canvas.stage);
			_clickIn = r.containsPoint(p);
			if (_clickIn) _canvas.stage.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove, false, 0, true);
		}
		
		private function _onMouseUp(event:MouseEvent):void {
			if (!_clickIn) return;
			
			_mouseUp(new XPoint(_canvas.globalToLocal(new Point(event.stageX, event.stageY))), event.shiftKey);
		}
		
		private function _mouseUp(pPoint:XPoint, pShift:Boolean):void {
			_clickIn = false;
			
			var time:Number = getTimer() - _lastMouseDownTime;
			var distance:Number = _lastMouseDownPoint.distanceTo(pPoint);
			var quick:Boolean = time <= 200 && distance <= 5;
			
			if (quick){
				var factor:Number = pShift ? 1.0 / 2 : 2;
				zoomBy(factor, pPoint);
			}
			
			_canvas.stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
		}

		private function _onMouseMove(event:MouseEvent):void {
			var point:XPoint = new XPoint(event.stageX, event.stageY);
			var delta:XPoint = point.minus(_lastPoint);
			
			_lastPoint = point;
			panBy(delta);
		}
		
		public function loadXml(pUrl:String, pXml:XML = null):void {
			var urlParts:Array = pUrl.split('/');
			var filename:String = urlParts[urlParts.length - 1];
			var lastDot:int = filename.lastIndexOf('.');
			if (lastDot > -1) {
				urlParts[urlParts.length - 1] = filename.slice(0, lastDot);
			}
			
			_tilesUrl = urlParts.join('/') + "_files/";
			
			if (pXml != null){
				_parseXml(pXml);
				
			} else {
				_loader = new XmlLoader(pUrl);
				_loader.onComplete.add(_onLoad);
				_loader.start();
			}
		}
		
		private function _onLoad(pSender:XmlLoader, pArgs:EventArgs):void {
			_parseXml(pSender.xml);
			_loader.dispose();
		}

		private function _parseXml(pXml:XML):void {
			var tileSize:uint = parseInt(pXml.attribute("TileSize")) || 256;
			var overlap:uint = parseInt(pXml.attribute("Overlap")) || 0;
			var format:String = pXml.attribute("Format") || "jpg";
			
			var imageSize:Size = new Size(parseInt(pXml..*::Size.attribute("Width")), parseInt(pXml..*::Size.attribute("Height")));
			
			//TODO: display rectangles
			init(imageSize, tileSize, overlap, format);
		}
		
		private function _update(pSender:FrameEventDispatcher, evt:TickEventArgs):void {
			var animated:Boolean = _viewPort.update();
			
			if (animated) {
				_drawer.update();

			} else if (_forceRedraw || _drawer.needsUpdate) {
				_drawer.update();
				_forceRedraw = false;
			}
		
			_animating = animated;
		}
	}
}
